/*****************************************************************************
* File Name: audio.c
*
* Version: 1.0
*
* Description:
* This file contains API for reading audio data through I2S interface
*
* Note:
* Defining DISABLE_AUDIO in platform.h to disable audio module
*
* Owner: MRAO
*
* Related Document:
* I2S Module data sheet
*
* Hardware Dependency:
* INMP441 Audio codec chip
*
* Code Tested With:
* 1. PSoC Creator 3.1 
* 2. ARM GCC 4.8.4
******************************************************************************
* Copyright (2015), Cypress Semiconductor Corporation.
******************************************************************************
* This software is owned by Cypress Semiconductor Corporation (Cypress) and is
* protected by and subject to worldwide patent protection (United States and
* foreign), United States copyright laws and international treaty provisions.
* Cypress hereby grants to licensee a personal, non-exclusive, non-transferable
* license to copy, use, modify, create derivative works of, and compile the
* Cypress Source Code and derivative works for the sole purpose of creating
* custom software in support of licensee product to be used only in conjunction
* with a Cypress integrated circuit as specified in the applicable agreement.
* Any reproduction, modification, translation, compilation, or representation of
* this software except as specified above is prohibited without the express
* written permission of Cypress.
*
* Disclaimer: CYPRESS MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH
* REGARD TO THIS MATERIAL, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 
* Cypress reserves the right to make changes without further notice to the
* materials described herein. Cypress does not assume any liability arising out
* of the application or use of any product or circuit described herein. Cypress
* does not authorize its products for use as critical components in life-support
* systems where a malfunction or failure may reasonably be expected to result in
* significant injury to the user. The inclusion of Cypress' product in a life-
* support systems application implies that the manufacturer assumes all risk of
* such use and in doing so indemnifies Cypress against all charges. Use may be
* limited by and subject to the applicable Cypress software license agreement.
*****************************************************************************/
#include "audio.h"
#include "debug.h"
#ifdef AUDIO_ADC_SAMPLE
#include <stdlib.h>
#include "battery.h"
#endif
#include "adpcm.h"

#ifndef DISABLE_AUDIO

#define Audio_I2S_FIFO_HALF_EMPTY_MASK                      (0x0C)

bool isAudioActive;
uint8 *bufferPtr = NULL;
uint8 currBuffer = 0;
uint8 readBuffer = 0;
Audio_Control_Packet audioControlPacket;

#ifdef AUDIO_SEND_FREQUENT_SYNC_PACKET
uint32 audioTimeStamp;
#endif /* AUDIO_SEND_FREQUENT_SYNC_PACKET */
/* Variable to count the number of nibbles stored in the buffer */
uint32 samplesInBuffer = 0;

/*Audio data lower byte*/
int32 prevSample = 0;

/*ADPCM encode index*/
int8 adpcmIndex = 0;

uint16 noPacketsDrop;
/*Audio data buffers*/
Audio_Data buffer[NUMBER_OF_BUFFERS]; 
uint16 ADCSampleDrop;

bool isFirstSample = false;/*Variable to check 1st audio sample*/
/*Index table*/
const int8 indexTable[16] = {
    -1, -1, -1, -1, 2, 4, 6, 8,
    -1, -1, -1, -1, 2, 4, 6, 8,
};

/*Step size look up table*/
const int16 stepSizeTable[89] = {
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
    19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
    50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};

#ifdef	AUDIO_ADC_SAMPLE
#ifndef DISABLE_AUDIO_NOISE_FILTER
int8 filter_noise_en,filter_noise_test;
uint8 noise_filter_factor;
uint16 noise_filter_delay=0;
#endif/*DISABLE_AUDIO_NOISE_FILTER*/ 

/* adc dc filter */
int32 adc_dc_estimate;
int32 adc_noise_filter=0;
int16 adc_result;

#endif/*AUDIO_ADC_SAMPLE*/


#ifdef AUDIO_ADC_SAMPLE
///////////////////////////////////////////////////////////////////////////////
/// \brief			dc_filter
/// \param			p
/// \param			x
/// \return 		x
/// \deprecated 
///////////////////////////////////////////////////////////////////////////////
int16 dc_filter(int32 *p, int16 x)
{
#if 0
    uint16 tmp = *p>>16;

      if(abs(tmp-x)> (300))
      {
          *p = (int32) (x << 16);
      }
      else
#endif
      {
          *p += ((((int32) x << 16) - *p) >> 10);
      }    
    
    x -= (*p >> 16);
	
    return x;   
}
#ifndef DISABLE_AUDIO_NOISE_FILTER
///////////////////////////////////////////////////////////////////////////////
/// \brief			NoiseFilter
/// \param			p
/// \param			x
/// \param			f
/// \return 		filter_en
/// \deprecated 
///////////////////////////////////////////////////////////////////////////////
int16 NoiseFilter(int32 *p, int16 x,uint8 f)
{
  //*p += ((((int32_t) x << (NOISE_FILTER_LIMT+2)) - *p) >> f);
  //*p = *p + ((x - *p)>>8);
  //*p = (*p * f + x * (16 -f))>>4;
  //return *p>>(NOISE_FILTER_LIMT+2);
  
  *p = (*p * f + x * (16 -f))>>4;
  return *p;
}

///////////////////////////////////////////////////////////////////////////////
/// \brief			SampleRms
/// \param			sample
/// \return 		filter_en
/// \deprecated 
///////////////////////////////////////////////////////////////////////////////
int SampleRms(int16 sample)

{
  static uint16 count=0;
	//static int16 sample_buf[NOISE_FILTER_LEN];
	int filter_en = -1;
	static uint8 rms_count;

	if(count< NOISE_FILTER_LEN)
  {
      count++;
      if(abs(sample) > NOISE_THRESHOLD )
      {
        rms_count++;
      }
  }
  else
  {
    count=0;
    if(rms_count < NOISE_COUNT_THRESHOLD)
    {
      filter_en = 1;  
    }
    
    rms_count = 0;
  }
  
  if(rms_count > NOISE_COUNT_THRESHOLD)
  {
		filter_en = 0;
    rms_count = 0;
  }

  return filter_en;
}
#endif

#endif/*AUDIO_ADC_SAMPLE*/
/*****************************************************************************
* Function Name: Audio_ADPCM_Encode()
******************************************************************************
* Summary:
* This function encodes audio data using ADPCM compression
*
* Parameters:
* audioSample - Audio sample data read through I2S component
*
* Return:
* None
*
* Theory:
* The ADPCM algorithm takes advantage of the high correlation between 
* consecutive speech samples, which enables future sample values to be 
* predicted. Instead of encoding the speech sample, ADPCM encodes the 
* difference between a predicted sample and the speech sample. This 
* method provides more efficient compression with a reduction in the 
* number of bits per sample, yet preserves the overall quality of the 
* speech signal.
*
* Side Effects:
* Updates the buffer with the compressed data.
* 
* Note:
* None
*****************************************************************************/
#if 0
static void Audio_ADPCM_Encode(uint16 audioSample)
{
    int8 adpcmCode=0;     
    int16 step,diff,diffq;

    if(isFirstSample)
    {
#ifdef AUDIO_SEND_FREQUENT_SYNC_PACKET
        audioTimeStamp = Timer_Get_Time_Stamp();
#endif /* AUDIO_SEND_FREQUENT_SYNC_PACKET */

        audioControlPacket.controlCode = 0xFE;
        audioControlPacket.controlDataLength = 4;
        audioControlPacket.controlData[0] = adpcmIndex;
        audioControlPacket.controlData[1] = (uint8)((prevSample>>BIT_8_POSITION) & LOWER_BYTE_MASK);
        audioControlPacket.controlData[2] = (uint8)(prevSample & LOWER_BYTE_MASK);
        audioControlPacket.controlBufferPoint = currBuffer;
        isFirstSample = false;
        samplesInBuffer = 0;
    }

    step = stepSizeTable[adpcmIndex];
    diff = audioSample - prevSample;

    /* Set the sign bit of the ADPCM code if necessary and find the absolute difference in value */
    if(diff < 0)
    {
        adpcmCode = BIT_3_MASK;
        diff = -diff;
    }

    diffq = step>>BIT_3_POSITION;
    /* Quantize the difference into the ADPCM code using the quantizer step size */
    if(diff >= step)
    {
        adpcmCode |= BIT_2_MASK;
        diff -= step;
        diffq += step;
    }
    step = step>>BIT_1_POSITION;

    if(diff >= step)
    {
        adpcmCode |= BIT_1_MASK;
        diff -= step;
        diffq += step;
    }
    step = step>>BIT_1_POSITION;
    
    if(diff >= step)
    {
        adpcmCode |= BIT_0_MASK;
        diff -= step;
        diffq += step;
    }
    /* Fixed predictor computes new predicted sample by adding the old predicted sample to predicted difference */
    if(adpcmCode & BIT_3_MASK)
    {
        prevSample -= diffq;
    }
    else
    {
        prevSample += diffq;
    }
    /* Check for overflow of the new predicted sample */
    if(prevSample > AUDIO_SAMPLE_MAX)
    {
        prevSample = AUDIO_SAMPLE_MAX;
    }
    else if(prevSample < AUDIO_SAMPLE_MIN)
    {
        prevSample = AUDIO_SAMPLE_MIN;
    }

    /* Find new quantizer step size index by adding the old index to a table lookup using the ADPCM code */
    adpcmIndex += indexTable[adpcmCode];

    /* Check for overflow of the new quantizer step size index */
    if(adpcmIndex < 0)
    {
        adpcmIndex = 0;
    }
    else if(adpcmIndex > ADPCM_INDEX_MAX)
    {
        adpcmIndex = ADPCM_INDEX_MAX;
    }

    if(!(samplesInBuffer & BIT_0_MASK))
    {
        *(bufferPtr) = (uint8)(adpcmCode & LOWER_NIBBLE_MASK);
    }
    else
    {
        *bufferPtr++ |= (((uint8)(adpcmCode & LOWER_NIBBLE_MASK))<<BIT_4_POSITION);
    }
    samplesInBuffer++;
    if(samplesInBuffer == (BUFFER_SIZE<<BIT_1_POSITION))
    {
        if(noPacketsDrop == 0)
        {
            buffer[currBuffer].audio_length = BUFFER_SIZE;
            currBuffer++;
            if(currBuffer == NUMBER_OF_BUFFERS)
            {
                currBuffer = 0;
            }
            if(readBuffer == currBuffer)
            {
                isFirstSample = true;
            }
#ifdef AUDIO_SEND_FREQUENT_SYNC_PACKET
            if(Timer_Time_Elapsed(audioTimeStamp, AUDIO_SYNC_INTERVAL))
            {
                isFirstSample = true;
            }
#endif /* AUDIO_SEND_FREQUENT_SYNC_PACKET */            
            
        }
        else
        {
            noPacketsDrop--;            

            if(noPacketsDrop == 0)
            {
                isFirstSample = true;
            }
        }
        buffer[currBuffer].audio_length = 0;
        bufferPtr = &buffer[currBuffer].audio_buffer[0];
        samplesInBuffer = 0;
    }
}
#endif
/*****************************************************************************
* Function Name: Audio_CallBack()
******************************************************************************
* Summary:
* This functions is a callback function when data is available in the Audio module
*
* Parameters:
* None
*
* Return:
* None
*
* Theory:
* Whenever the data is received by the I2S component the Audio_CallBack() function is
* called. This function need to read the data from the I2S component and encodes it
*
* Side Effects:
* None
* 
* Note:
* None
*****************************************************************************/
void Audio_Battery_CallBack(void)
{
#ifndef AUDIO_ADC_SAMPLE
    Audio_ADPCM_Encode(((uint16)Audio_I2S_ReadByte(0)<<BIT_8_POSITION) | ((uint16)Audio_I2S_ReadByte(0)));
#else
    uint32 intr_status;
    uint32 range_status;

		uint16 result;

    /* Read interrupt status registers */
    intr_status = ADC_SAR_SEQ_SAR_INTR_MASKED_REG;
    /* Check for End of Scan interrupt */
    if((intr_status & ADC_SAR_SEQ_EOS_MASK) != 0u)
    {
        //Debug_Print(DEBUG_MESSAGE_LEVEL_4,"Audio_Battery_CallBack\r\n");
        /* Read range detect status */
        range_status = ADC_SAR_SEQ_SAR_RANGE_INTR_MASKED_REG;
        /* Verify that the conversion result met the condition Low_Limit <= Result < High_Limit  */
        //Debug_Print(DEBUG_MESSAGE_LEVEL_4,"range_status = 0x%x\r\n",range_status);  
        if((range_status & (uint32)(1ul << CH0_N)) != 0u) 
        {
#ifndef AUDIO_SIMULATED_DATA
          //Debug_Print(DEBUG_MESSAGE_LEVEL_4,"range_status\r\n");
          result = ADC_SAR_SEQ_GetResult16(CH0_N);
          if(!ADCSampleDrop)
          {
            adc_result = dc_filter(&adc_dc_estimate,result);
            //Debug_Print(DEBUG_MESSAGE_LEVEL_5,"dc_filter=0x%x\r\n",adc_dc_estimate);
            
  #ifndef DISABLE_AUDIO_NOISE_FILTER 
            filter_noise_en = SampleRms(adc_result);

            if(filter_noise_en == 1)
            {
                //if(noise_filter_delay)noise_filter_delay--;
                //else
                {
                  //noise_filter_factor = NOISE_FILTER_LIMT-2;
                  noise_filter_factor+=2;
                  if(noise_filter_factor >= NOISE_FILTER_LIMT)
                    noise_filter_factor = NOISE_FILTER_LIMT-2;
                  //adc_result = 0;
                }
                
            }
            else if(filter_noise_en == 0)
            {
                noise_filter_factor = 0;
                //noise_filter_delay = NOISE_FILTER_INTERVAL*10;
            }

  					adc_result = NoiseFilter(&adc_noise_filter,adc_result,noise_filter_factor);
  #endif	  		
            Audio_ADPCM_Encode(adc_result<<4);
          }
          else
            ADCSampleDrop--;


					//DEBUG_PIN_TOGGLE();
#else
					ADC_SAR_SEQ_GetResult16(CH0_N);
					result = (uint16)((uint16)rawData[count] | ((uint16)rawData[count+1]<<8));
					Audio_ADPCM_Encode(result);
					count+=2;
					if(count == 352)
					{
							count = 0;
					}

#endif

        }    

        /* Clear range detect status */
        ADC_SAR_SEQ_SAR_RANGE_INTR_REG = range_status;
        //ADC_SAR_SEQ_StopConvert();
    }    
#ifndef DISABLE_BATTERY
    if((intr_status & ADC_SAR_SEQ_INJ_EOC_MASK) != 0u)
    {
        //result[TEMP_CH] = ADC_SAR_SEQ_GetResult16(TEMP_CH);
        batteryDataReady |= BD_RDY;
    }  
#endif

    /* Clear handled interrupt */
    ADC_SAR_SEQ_SAR_INTR_REG = intr_status;


		

#endif/*AUDIO_ADC_SAMPLE*/
}

/*****************************************************************************
* Function Name: Audio_Init()
******************************************************************************
* Summary:
* This functions is used to initialize the audio module
*
* Parameters:
* None
*
* Return:
* None
*
* Theory:
* Initialize the I2S component
*
* Side Effects:
* None
* 
* Note:
* None
*****************************************************************************/
void Audio_Init(void)
{


#ifndef AUDIO_ADC_SAMPLE
    Audio_I2S_Init();
    Audio_I2S_RX_AUX_CONTROL_REG = (Audio_I2S_RX_AUX_CONTROL_REG | Audio_I2S_FIFO_HALF_EMPTY_MASK);
    Audio_I2S_Interrupt_StartEx(Audio_CallBack);
    Audio_I2S_Interrupt_Disable();
    Audio_I2S_Clock_Stop();    
    Audio_Codec_Disable();
    isAudioActive = false;

#else
#ifdef DISABLE_BATTERY
		ADC_Start(); 
		ADC_Sleep();
    ADC_SAR_SEQ_IRQ_StartEx(Audio_Battery_CallBack);
#endif  
    Timer_1_Init();
    adc_dc_estimate = 0x05a67e0dul;

#endif/*AUDIO_ADC_SAMPLE*/
    Debug_Print(DEBUG_MESSAGE_LEVEL_5,"Audio_Init\r\n");
}

/*****************************************************************************
* Function Name: Audio_Start()
******************************************************************************
* Summary:
* This functions is used to start reading the audio data from the codec
*
* Parameters:
* None
*
* Return:
* None
*
* Theory:
* Starts the I2S component and reset the variables
*
* Side Effects:
* Reset the currBuffer, readBuffer, samplesInBuffer, isLowerByteFilled and 
* bufferPtr to initial values
* 
* Note:
* None
*****************************************************************************/
void Audio_Start(void)
{
#ifndef AUDIO_ADC_SAMPLE
    Audio_Codec_Enable();
    Audio_I2S_EnableRx();
    Audio_I2S_Interrupt_Enable();
    Audio_I2S_Clock_Start();
    samplesInBuffer = 0;
#if (NUMBER_OF_PACKET_TO_DROP == 0)
    isFirstSample = true;
#endif /* (NUMBER_OF_PACKET_TO_DROP == 0) */
    currBuffer = 0;
    readBuffer = 0;
    prevSample = 0;
    adpcmIndex = 0;
    noPacketsDrop = NUMBER_OF_PACKET_TO_DROP;
    bufferPtr = buffer[currBuffer].audio_buffer;
    isAudioActive = true;
    Audio_I2S_Start();
#else
		samplesInBuffer = 0;
		isFirstSample = true;
		currBuffer = 0;
		readBuffer = 0;
		prevSample = 0;
		adpcmIndex = 0;
		bufferPtr = buffer[currBuffer].audio_buffer;
#ifdef	CY_OPAMP_Opamp_1_H
		Opamp_1_Start();
#endif
#ifdef	CY_OPAMP_Opamp_1_H
    Opamp_2_Start();
#endif  
    Audio_EN_Write(0);
		Timer_1_Enable();
		ADC_Wakeup();
#ifdef AUDIO_SIMULATED_DATA
				count = 0;
#endif
		isAudioActive = true;
    ADCSampleDrop = NUMBER_OF_PACKET_TO_DROP;
		Debug_Print(DEBUG_MESSAGE_LEVEL_4,"Audio_Start\r\n");

#endif/*AUDIO_ADC_SAMPLE*/
}

/*****************************************************************************
* Function Name: Audio_Poll()
******************************************************************************
* Summary:
* This functions is polls if any audio data is available
*
* Parameters:
* None
*
* Return:
* None
*
* Theory:
* Checks if any buffer is filled and raises the event
*
* Side Effects:
* None
* 
* Note:
* None
*****************************************************************************/
void Audio_Poll(void)
{
    if((readBuffer != currBuffer) || (audioControlPacket.controlCode != 0))
    {
        DEVICE_POST_EVENT(AUDIO_EVENT);
    }
#ifndef AUDIO_ADC_SAMPLE
    else if(Audio_I2S_RX_CH0_STS_REG & Audio_I2S_RX_FIFO_OVERFLOW)
    {
        /* Error Mechanism to recover I2S */
        Audio_I2S_Stop();
        Audio_I2S_Init();
        Audio_I2S_EnableRx();
        samplesInBuffer = 0;
        prevSample = 0;
        adpcmIndex = 0;
        isFirstSample = true;
        bufferPtr = buffer[currBuffer].audio_buffer;
        Audio_I2S_Start();
    }
#endif/*AUDIO_ADC_SAMPLE*/
}

/*****************************************************************************
* Function Name: Audio_Get_Report()
******************************************************************************
* Summary:
* This functions is forms the packet for the audio data if available
*
* Parameters:
* report pointer- Report structure will be filled if any audio event occurs
*
* Return:
* ReportType - AUDIO_REPORT If any audio packet is available else NO_REPORT
*
* Theory:
* Updates the report structure with the audio data
*
* Side Effects:
* Increments the readbuffer count
* 
* Note:
* None
*****************************************************************************/
ReportType Audio_Get_Report(Report *report)
{
    ReportType reportType = NO_REPORT;
    uint8 i=0;
    uint8 j=0;

    if(DEVICE_GET_EVENT(AUDIO_EVENT))
    {
        if(audioControlPacket.controlCode != 0)
        {
            reportType |= AUDIO_CONTROL_REPORT;
            memcpy(&report->audio_control_packet.controlCode, &audioControlPacket.controlCode, sizeof(Audio_Control_Packet));
            audioControlPacket.controlCode = 0;
        }

        while((readBuffer != currBuffer) && (i < AUDIO_BUFFER))
        {
            if(readBuffer == report->audio_control_packet.controlBufferPoint)
            {
                j = i;
            }
            report->audio_report.audio_data[i++] = &buffer[readBuffer++];
            if(readBuffer == NUMBER_OF_BUFFERS)
            {
                readBuffer = 0;
            }
        }

        if(i > 0)
        {
            report->audio_control_packet.controlBufferPoint = j;
            report->audio_report.noOfAudioPacket = i;
            reportType |= AUDIO_REPORT;
        }
    }
    return reportType;
}

/*****************************************************************************
* Function Name: Audio_Stop()
******************************************************************************
* Summary:
* This functions is used to stop audio module
*
* Parameters:
* None
*
* Return:
* None
*
* Theory:
* Stops the I2S block
*
* Side Effects:
* None
* 
* Note:
* None
*****************************************************************************/
void Audio_Stop(void)
{
#ifndef AUDIO_ADC_SAMPLE
    buffer[currBuffer].audio_length = samplesInBuffer>>BIT_1_POSITION;
    Audio_I2S_Interrupt_Disable();
    Audio_I2S_Stop();
    Audio_I2S_DisableRx();
    Audio_I2S_ClearRxFIFO();
    Audio_I2S_Clock_Stop();
    Audio_Codec_Disable();
    isAudioActive = false;
#else
		buffer[currBuffer].audio_length = samplesInBuffer>>BIT_1_POSITION;
		Timer_1_Stop();
#ifdef CY_OPAMP_Opamp_1_H
		Opamp_1_Stop();		
#endif
#ifdef CY_OPAMP_Opamp_2_H
		Opamp_2_Stop();		
#endif    
		Audio_EN_Write(1);
		ADC_Sleep();
		isAudioActive = false;
    Debug_Print(DEBUG_MESSAGE_LEVEL_4,"Audio_Stop\r\n");
#endif/*AUDIO_ADC_SAMPLE*/
}

#endif /* DISABLE_AUDIO */
